package com.bidr.kernel.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.bidr.kernel.common.func.GetFunc;
import com.bidr.kernel.common.func.SetFunc;

import java.io.Serializable;
import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;

/**
 * Title: LambdaUtil
 * Description: Copyright: Copyright (c) 2022 Company: Sharp Ltd.
 *
 * @author Sharp
 * @since 2023/03/27 11:14
 */

public class LambdaUtil {

    private static final WeakConcurrentMap<String, SerializedLambda> cache = new WeakConcurrentMap<>();
    private static final String FUNCTION_PREFIX = "()L";

    /**
     * 通过对象的方法或类的静态方法引用，获取lambda实现类
     * 传入lambda无参数但含有返回值的情况能够匹配到此方法：
     * <ul>
     * <li>引用特定对象的实例方法：<pre>{@code
     * MyTeacher myTeacher = new MyTeacher();
     * Class<MyTeacher> supplierClass = LambdaUtil.getRealClass(myTeacher::getAge);
     * Assert.assertEquals(MyTeacher.class, supplierClass);
     * }</pre></li>
     * <li>引用静态无参方法：<pre>{@code
     * Class<MyTeacher> staticSupplierClass = LambdaUtil.getRealClass(MyTeacher::takeAge);
     * Assert.assertEquals(MyTeacher.class, staticSupplierClass);
     * }</pre></li>
     * </ul>
     * 在以下场景无法获取到正确类型
     * <pre>{@code
     * // 枚举测试，只能获取到枚举类型
     * Class<Enum<?>> enumSupplierClass = LambdaUtil.getRealClass(LambdaUtil.LambdaKindEnum.REF_NONE::ordinal);
     * Assert.assertEquals(Enum.class, enumSupplierClass);
     * // 调用父类方法，只能获取到父类类型
     * Class<Entity<?>> superSupplierClass = LambdaUtil.getRealClass(myTeacher::getId);
     * Assert.assertEquals(Entity.class, superSupplierClass);
     * // 引用父类静态带参方法，只能获取到父类类型
     * Class<Entity<?>> staticSuperFunctionClass = LambdaUtil.getRealClass(MyTeacher::takeId);
     * Assert.assertEquals(Entity.class, staticSuperFunctionClass);
     * }</pre>
     *
     * @param func lambda
     * @param <R>  类型
     * @return lambda实现类
     * @throws IllegalArgumentException 如果是不支持的方法引用，抛出该异常，见
     * @author VampireAchao
     * @since 5.8.0
     */
    public static <T, R> Class<T> getRealClassByGetFunc(GetFunc<T, R> func) {
        final SerializedLambda lambda = resolveByGetFunc(func);
        checkLambdaTypeCanGetClass(lambda.getImplMethodKind());
        return ClassUtil.loadClass(lambda.getImplClass());
    }

    public static <T, R> Class<R> getTargetClass(SetFunc<T, R> func) {
        final SerializedLambda lambda = resolve(func);
        checkLambdaTypeCanGetClass(lambda.getImplMethodKind());
        return ClassUtil.loadClass(lambda.getImplClass());
    }

    /**
     * 解析lambda表达式,加了缓存。
     * 该缓存可能会在任意不定的时间被清除
     *
     * @param <T,  R>  Lambda类型
     * @param func 需要解析的 lambda 对象（无参方法）
     * @return 返回解析后的结果
     */
    public static <T, R> SerializedLambda resolve(SetFunc<T, ?> func) {
        return _resolve(func);
    }

    /**
     * 检查是否为支持的类型
     *
     * @param implMethodKind 支持的lambda类型
     * @throws IllegalArgumentException 如果是不支持的方法引用，抛出该异常
     */
    private static void checkLambdaTypeCanGetClass(int implMethodKind) {
        if (implMethodKind != MethodHandleInfo.REF_invokeVirtual &&
                implMethodKind != MethodHandleInfo.REF_invokeStatic) {
            throw new IllegalArgumentException("该lambda不是合适的方法引用");
        }
    }

    /**
     * 解析lambda表达式,加了缓存。
     * 该缓存可能会在任意不定的时间被清除。
     *
     * <p>
     * 通过反射调用实现序列化接口函数对象的writeReplace方法，从而拿到{@link SerializedLambda}<br>
     * 该对象中包含了lambda表达式的所有信息。
     * </p>
     *
     * @param func 需要解析的 lambda 对象
     * @return 返回解析后的结果
     */
    private static SerializedLambda _resolve(Serializable func) {
        return cache.computeIfAbsent(func.getClass().getName(), (key) -> ReflectUtil.invoke(func, "writeReplace"));
    }

    /**
     * 通过对象的方法或类的静态方法引用，然后根据{@link SerializedLambda#getInstantiatedMethodType()}获取lambda实现类<br>
     * 传入lambda有参数且含有返回值的情况能够匹配到此方法：
     * <ul>
     * <li>引用特定类型的任意对象的实例方法：<pre>{@code
     * Class<MyTeacher> functionClass = LambdaUtil.getRealClass(MyTeacher::getAge);
     * Assert.assertEquals(MyTeacher.class, functionClass);
     * }</pre></li>
     * <li>引用静态带参方法：<pre>{@code
     * Class<MyTeacher> staticFunctionClass = LambdaUtil.getRealClass(MyTeacher::takeAgeBy);
     * Assert.assertEquals(MyTeacher.class, staticFunctionClass);
     * }</pre></li>
     * </ul>
     *
     * @param func lambda
     * @param <P>  方法调用方类型
     * @param <R>  返回值类型
     * @return lambda实现类
     * @throws IllegalArgumentException 如果是不支持的方法引用，抛出该异常，见
     * @author VampireAchao
     * @since 5.8.0
     */
    public static <P, R> Class<P> getRealClass(SetFunc<P, R> func) {
        final SerializedLambda lambda = resolve(func);
        checkLambdaTypeCanGetClass(lambda.getImplMethodKind());
        final String instantiatedMethodType = lambda.getInstantiatedMethodType();
        return ClassUtil.loadClass(
                StrUtil.sub(instantiatedMethodType, 2, StrUtil.indexOf(instantiatedMethodType, ';')));
    }

    /**
     * 获取lambda表达式Getter或Setter函数（方法）对应的字段名称，规则如下：
     * <ul>
     *     <li>getXxxx获取为xxxx，如getName得到name。</li>
     *     <li>setXxxx获取为xxxx，如setName得到name。</li>
     *     <li>isXxxx获取为xxxx，如isName得到name。</li>
     *     <li>其它不满足规则的方法名抛出{@link IllegalArgumentException}</li>
     * </ul>
     *
     * @param <T,  R>  Lambda类型
     * @param func 函数（无参方法）
     * @return 方法名称
     * @throws IllegalArgumentException 非Getter或Setter方法
     * @since 5.7.10
     */
    public static <T, R> String getFieldName(SetFunc<T, ?> func) throws IllegalArgumentException {
        return cn.hutool.core.bean.BeanUtil.getFieldName(getMethodName(func));
    }

    /**
     * 获取lambda表达式函数（方法）名称
     *
     * @param <P>  Lambda参数类型
     * @param func 函数（无参方法）
     * @return 函数名称
     */
    public static <P> String getMethodName(SetFunc<P, ?> func) {
        return resolve(func).getImplMethodName();
    }

    public static <T, R> Field getField(SetFunc<T, ?> func) throws IllegalArgumentException {
        String fieldName = BeanUtil.getFieldName(getMethodName(func));
        return ReflectionUtil.getField(getRealClass(func), fieldName);
    }

    /**
     * 获取lambda表达式Getter或Setter函数（方法）对应的字段名称，规则如下：
     * <ul>
     *     <li>getXxxx获取为xxxx，如getName得到name。</li>
     *     <li>setXxxx获取为xxxx，如setName得到name。</li>
     *     <li>isXxxx获取为xxxx，如isName得到name。</li>
     *     <li>其它不满足规则的方法名抛出{@link IllegalArgumentException}</li>
     * </ul>
     *
     * @param <T,  R>  Lambda类型
     * @param func 函数（无参方法）
     * @return 方法名称
     * @throws IllegalArgumentException 非Getter或Setter方法
     * @since 5.7.23
     */
    public static <T, R> String getFieldNameByGetFunc(GetFunc<T, R> func) throws IllegalArgumentException {
        return BeanUtil.getFieldName(getMethodNameByGetFunc(func));
    }

    //region Private methods

    /**
     * 获取lambda表达式函数（方法）名称
     *
     * @param <R>  Lambda返回类型
     * @param func 函数（无参方法）
     * @return 函数名称
     * @since 5.7.23
     */
    public static <T, R> String getMethodNameByGetFunc(GetFunc<T, R> func) {
        return resolveByGetFunc(func).getImplMethodName();
    }

    /**
     * 解析lambda表达式,加了缓存。
     * 该缓存可能会在任意不定的时间被清除
     *
     * @param <R>  Lambda返回类型
     * @param func 需要解析的 lambda 对象（无参方法）
     * @return 返回解析后的结果
     * @since 5.7.23
     */
    public static <T, R> SerializedLambda resolveByGetFunc(GetFunc<T, R> func) {
        return _resolve(func);
    }

    public static <T, R> Field getFieldByGetFunc(GetFunc<T, R> func) throws IllegalArgumentException {
        String fieldName = BeanUtil.getFieldName(getMethodNameByGetFunc(func));
        return ReflectionUtil.getField(getRealClassByGetFunc(func), fieldName);
    }

    public static <T, R> Field getField(SFunction<T, ?> func) throws IllegalArgumentException {
        String fieldName = BeanUtil.getFieldName(getMethodName(func));
        return ReflectionUtil.getField(getRealClass(func), fieldName);
    }


    public static <T> Class<T> getRealClass(SFunction<T, ?> func) {
        final SerializedLambda lambda = resolveSFunction(func);
        checkLambdaTypeCanGetClass(lambda.getImplMethodKind());
        return ClassUtil.loadClass(lambda.getImplClass());
    }

    public static <T, R> SerializedLambda resolveSFunction(SFunction<T, R> func) {
        return _resolve(func);
    }

    public static <T, R> String getFieldName(SFunction<T, ?> func) throws IllegalArgumentException {
        return BeanUtil.getFieldName(getMethodName(func));
    }

    public static <T, R> String getMethodName(SFunction<T, R> func) {
        return resolveSFunction(func).getImplMethodName();
    }

    public static <T, R> R getValue(SFunction<T, R> func, Object obj) {
        if (FuncUtil.isNotEmpty(obj)) {
            Class<R> clazz = LambdaUtil.getMethodSignatureClass(func);
            return JsonUtil.readJson(obj, clazz);
        }
        return null;
    }

    public static <T, R> R getValue(GetFunc<T, R> func, Object obj) {
        if (FuncUtil.isNotEmpty(obj)) {
            Class<R> clazz = LambdaUtil.getMethodSignatureClass(func);
            return JsonUtil.readJson(obj, clazz);
        }
        return null;
    }

    public static <T, R> R getValue(T entity, SFunction<T, R> func) {
        if (FuncUtil.isNotEmpty(entity)) {
            Class<R> clazz = LambdaUtil.getMethodSignatureClass(func);
            String fieldName = LambdaUtil.getFieldName(func);
            return ReflectionUtil.getValue(entity, fieldName, clazz);
        }
        return null;
    }

    public static <T, R> R getValue(T entity, GetFunc<T, R> func) {
        if (FuncUtil.isNotEmpty(entity)) {
            Class<R> clazz = LambdaUtil.getMethodSignatureClass(func);
            String fieldName = LambdaUtil.getFieldNameByGetFunc(func);
            return ReflectionUtil.getValue(entity, fieldName, clazz);
        }
        return null;
    }

    public static <T, R> void setValue(Object entity, SFunction<T, R> func, Object obj) {
        Object value = LambdaUtil.getValue(func, obj);
        String fieldName = LambdaUtil.getFieldName(func);
        ReflectionUtil.setValue(entity, fieldName, value);
    }

    public static <T, R> void setValue(Object entity, GetFunc<T, R> func, Object obj) {
        Object value = LambdaUtil.getValue(func, obj);
        String fieldName = LambdaUtil.getFieldNameByGetFunc(func);
        ReflectionUtil.setValue(entity, fieldName, value);
    }

    /**
     * 类型String ()L 开头 ;结尾 不能解析
     *
     * @param func lambda 函数
     * @param <T>  实体类型
     * @param <R>  字段类型
     * @return 字段类型Class
     */
    public static <T, R> Class<R> getMethodSignatureClass(SFunction<T, R> func) {
        final SerializedLambda lambda = resolveSFunction(func);
        checkLambdaTypeCanGetClass(lambda.getImplMethodKind());
        String name = lambda.getImplMethodSignature();
        Class<R> res;
        try {
            res = ClassUtil.loadClass(name);
        } catch (Exception e) {
            res = ClassUtil.loadClass(name.substring(FUNCTION_PREFIX.length(), name.length() - 1));
        }
        return res;
    }

    /**
     * 类型String ()L 开头 ;结尾 不能解析
     *
     * @param func lambda 函数
     * @param <T>  实体类型
     * @param <R>  字段类型
     * @return 字段类型Class
     */
    public static <T, R> Class<R> getMethodSignatureClass(GetFunc<T, R> func) {
        final SerializedLambda lambda = resolveByGetFunc(func);
        checkLambdaTypeCanGetClass(lambda.getImplMethodKind());
        String name = lambda.getImplMethodSignature();
        Class<R> res;
        try {
            res = ClassUtil.loadClass(name);
        } catch (Exception e) {
            res = ClassUtil.loadClass(name.substring(FUNCTION_PREFIX.length(), name.length() - 1));
        }
        return res;
    }
}

